ZerMatt - GoogleCTF 2023
難読化されたluaのコードが与えられる
中身は以下のようなフラグチェッカー
実行にはlua53が必要
code:shell
$ lua zermatt.lua
_____ _ ___ _____ ____
| __|___ ___ ___| |___| |_ _| __|
| | | . | . | . | | -_| -< | | | __|
|_____|___|___|_ |_|___|___| |_| |_|
|___| ZerMatt - misc
hogehoge
LOSE
手で難読化を解除していくと、おおよそ以下のようなコードが得られる
独自VMの実装のように見える
code: lua
--[[
Copyright 2023 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
]] do
local function v23(program, v29, ...)
local PC = 1;
local v33 = 0;
local program = string.gsub(string.sub(program, 5), '..', function(v86)
if (string.byte(v86, 2) == 79) then
v33 = tonumber(string.sub(v86, 1, 1));
return "";
else
local v110 = string.char(tonumber(v86, 16));
if v33 then
local v138 = string.rep(v110, v33);
v33 = nil;
return v138;
else
return v110;
end
end
end);
function v34(v77, v78, v79)
if v79 then
local v102 = (v77 / (2 ^ (v78 - 1))) % (2 ^ (v79 - v78 + 1));
return v102 - (v102 % 1);
else
local v105 = 2 ^ (v78 - 1);
return (((v77 % (v105 + v105)) >= v105) and 1) or 0;
end
end
function read_u8()
local v72 = string.byte(program, PC, PC);
PC = PC + 1;
return v72;
end
function read_u16()
local v75, v76 = string.byte(program, PC, PC + 2);
PC = PC + 2;
return (v76 * 256) + v75;
end
function read_u32()
local v82, v83, v84, v85 = string.byte(program, PC, PC + 4);
PC = PC + 4;
return (v85 * 16777216) + (v84 * 65536) + (v83 * 256) + v82;
end
function v38()
local v94 = read_u32();
local v95 = read_u32();
local v96 = 1;
local v97 = (v34(v95, 1, 20) * 4294967296) + v94;
local v98 = v34(v95, 21, 31);
local v99 = ((v34(v95, 32) == 1) and -1) or 1;
if (v98 == 0) then
if (v97 == 0) then
return 0;
else
v96 = 0;
end
elseif (v98 == 2047) then
return ((v97 == 0) and (v99 * (1 / 0))) or (v99 * NaN);
end
return math.ldexp(v99, v98 - 1023) * (v96 + (v97 / (2 ^ 52)));
end
function v39(v87)
if not v87 then
v87 = read_u32();
if (v87 == 0) then return ""; end
end
local v90 = string.sub(program, PC, (PC + v87) - 1);
PC = PC + v87;
local v91 = {};
v91v163 = string.char(string.byte(string.sub(v90, v163, v163))); end
return table.concat(v91);
end
function v41(...) return {...}, select("#", ...); end
function v42()
local v54 = 3;
local v55 = 0;
local v56 = {};
local v57 = {};
local v58 = {};
local v59 = {v56, v57, nil, v58};
local v60 = read_u32();
local v61 = {};
for v143 = 1, v60 do
local v147 = nil;
local v146 = read_u8();
if (v146 == 1) then
v147 = read_u8() ~= 0;
elseif (v146 == 2) then
v147 = v38();
elseif (v146 == 3) then
v147 = v39();
end
end
for v148 = 1, read_u32() do
local v151 = read_u8();
if (v34(v151, 1, 1) == 0) then
local v178 = v34(v151, 2, 3);
local v179 = v34(v151, 4, 6);
local v180 = {read_u16(), read_u16(), nil, nil};
if (v178 == 0) then
elseif (v178 == 1) then
elseif (v178 == (2 + 0)) then
v1803 = read_u32() - 65536; elseif (v178 == (5 - 2)) then
v1803 = read_u32() - 65536; end
if (v34(v179, 1, 1) == 1) then v1802 = v61[v1802]; end if (v34(v179, 2, 2) == 1) then v1803 = v61[v1803]; end if (v34(v179, 3, 3) == 1) then v1804 = v61[v1804]; end end
end
for v152 = 1, read_u32() do v57v152 - 1 = v42(); end for v154 = 1, read_u32() do v58v154 = read_u32(); end return v59;
end
function v43(v62, v63, v64)
local v66 = 1;
return function(...)
local v157 = 3;
local v158 = 1;
local v159 = -1;
local v160 = {...};
local v161 = select("#", ...) - 1;
local v162 = nil;
function v162()
local v184 = 1;
local v185 = v67;
local v186 = v68;
local v187 = v69;
local v188 = v41;
local v189 = {};
local v190 = {};
local v191 = {};
for v199 = 0, v161 do
if (v199 >= v187) then
else
end
end
local v192 = (v161 - v187) + 1;
if (v194 == 0) then
do return table.unpack(v191, v225, v159); end
elseif (v194 == 1) then
v191[v1932] = v191[v1933] - v1934; elseif (v194 == 2) then
v191[v1932] = v191[v1933] % v1934; elseif (v194 == 3) then
elseif (v194 == 4) then
if (v191[v1932] == v191[v1934]) then v158 = v158 + 1;
else
end
elseif (v194 == 5) then
v191[v1932] = v191[v1933] + v1934; elseif (v194 == 6) then
v191[v1933] = v191[v1933]; elseif (v194 == 7) then
elseif (v194 == 8) then
elseif (v194 == 9) then
local v246 = {};
local v245 = setmetatable({}, {
end,
end
});
v158 = v158 + 1;
else
end
end
v191[v1932] = v43(v244, v245, v64); elseif (v194 == 10) then
local v251 = v191v249 + v250; if (v250 > 0) then
end
end
elseif (v194 == 11) then
v191v254 = v191v254(table.unpack(v191, v254 + 1, v159)); elseif (v194 == 12) then
do return; end
elseif (v194 == 13) then
v191[v1932] = v63[v1933]; elseif (v194 == 14) then
elseif (v194 == 15) then
local v229, v230 = v188(v191v228(table.unpack(v191, v228 + 1, v1933))); v159 = (v230 + v228) - 1;
local v231 = 0;
for v304 = v228, v159 do
v231 = v231 + 1;
end
elseif (v194 == 16) then
v159 = (v263 + v261) - 1;
local v264 = 0;
for v332 = v261, v159 do
v264 = v264 + 1;
end
elseif (v194 == 17) then
v64[v1933] = v191[v1932]; elseif (v194 == 18) then
elseif (v194 == 19) then
v191[v1932] = v191[v1933] % v191[v1934]; elseif (v194 == 20) then
local v277, v278 = v188(v191v276(table.unpack(v191, v276 + 1, v159))); v159 = (v278 + v276) - 1;
local v279 = 0;
for v335 = v276, v159 do
v279 = v279 + 1;
end
elseif (v194 == 21) then
if (v273 > 0) then
else
end
else
end
elseif (v194 == 22) then
v158 = v158 + 1;
else
end
elseif (v194 == 23) then
v191[v1932] = v1933 + v191[v1934]; elseif (v194 == 24) then
v191[v1932] = v191[v1933][v1934]; elseif (v194 == 25) then
elseif (v194 == 26) then
do return v191program8(table.unpack(v191, program8 + 1, v1933)); end elseif (v194 == 27) then
v191[v1932] = v64[v1933]; elseif (v194 == 28) then
v191v293(table.unpack(v191, v293 + 1, v159)); elseif (v194 == 29) then
end
v158 = v158 + 1;
end
_G'A', _G'B' = v41(pcall(v162)); error('Script error at .. v183 .. ':' .. _G'A'2 + 0); else
return table.unpack(_G'A', 2, _G'B'); end
end;
end
return v43(v42(), {}, v29)(...);
end
v23("MATT1C3O0003063O00737472696E6703043O006368617203043O00627974652O033O0073756203053O0062697433322O033O0062697403043O0062786F7203053O007461626C6503063O00636F6E63617403063O00696E7365727403023O00696F03053O00777269746503293O00205O5F9O204O205F5O203O5F205O5F204O5F200A03293O007C3O202O5F7C3O5F203O5F203O5F7C207C3O5F7C3O207C5F3O205F7C2O202O5F7C0A03293O007C2O207C2O207C202E207C202E207C202E207C207C202D5F7C202D3C2O207C207C207C2O202O5F7C0A03293O007C5O5F7C3O5F7C3O5F7C5F2O207C5F7C3O5F7C3O5F7C207C5F7C207C5F7C3O200A032A3O009O205O207C3O5F7C7O205A65724D612O74202D206D697363200A03023O00409103083O007EB1A3BB4586DBA703013O007303043O007265616403373O00DF17EB31E4E81CC12FC4EF37F223D1C334CC39FAF22CD915C4C321D43EC0FF2CC92FFAFE22DE2FFAEF22C32EC7F33BF22FD6FF22DD2FD803053O009C43AD4AA503053O007072696E742O033O00711D9903073O002654D72976DC4603043O00D27F250703053O009E30764272004A3O00121B3O00013O0020185O000200121B000100013O00201800010001000300121B000200013O00201800020002000400121B000300053O0006160003000A000100010004033O000A000100121B000300063O00201800040003000700121B000500083O00201800050005000900121B000600083O00201800060006000A00060900073O000100062O00063O00064O00068O00063O00044O00063O00014O00063O00024O00063O00053O00121B0008000B3O00201800080008000C00120E0009000D4O000700080002000100121B0008000B3O00201800080008000C00120E0009000E4O000700080002000100121B0008000B3O00201800080008000C00120E0009000F4O000700080002000100121B0008000B3O00201800080008000C00120E000900104O000700080002000100121B0008000B3O00201800080008000C00120E000900114O000700080002000100121B0008000B3O00201800080008000C2O0006000900073O00120E000A00123O00120E000B00134O000F0009000B4O001C00083O000100121B0008000B3O0020180008000800152O001D000800010002001211000800143O00121B000800144O0006000900073O00120E000A00163O00120E000B00174O00190009000B000200060400080043000100090004033O0043000100121B000800184O0006000900073O00120E000A00193O00120E000B001A4O000F0009000B4O001C00083O00010004033O0049000100121B000800184O0006000900073O00120E000A001B3O00120E000B001C4O000F0009000B4O001C00083O00012O000C3O00013O00013O00023O00026O00F03F026O00704002284O000800025O00120E000300014O001200045O00120E000500013O0004150003002300012O000D00076O0006000800024O000D000900014O000D000A00024O000D000B00034O000D000C00044O0006000D6O0006000E00063O002005000F000600012O000F000C000F4O000B000B3O00022O000D000C00034O000D000D00044O0006000E00013O002001000F000600012O0012001000014O0013000F000F0010001017000F0001000F0020010010000600012O0012001100014O00130010001000110010170010000100100020050010001000012O000F000D00104O0014000C6O000B000A3O0002002002000A000A00022O00100009000A4O001C00073O000100040A0003000500012O000D000300054O0006000400024O001A000300046O00036O000C3O00017O00283O00093O000A3O000A3O000A3O000A3O000B3O000B3O000B3O000B3O000B3O000B3O000B3O000B3O000B3O000B3O000B3O000B3O000B3O000B3O000B3O000B3O000B3O000B3O000B3O000B3O000B3O000B3O000B3O000B3O000B3O000B3O000B3O000B3O000B3O000A3O000D3O000D3O000D3O000D3O000E3O004A3O00013O00013O00023O00023O00033O00033O00043O00043O00043O00043O00053O00063O00063O00073O00073O000E3O000E3O000E3O000E3O000E3O000E3O000E3O000F3O000F3O000F3O000F3O00103O00103O00103O00103O00113O00113O00113O00113O00123O00123O00123O00123O00133O00133O00133O00133O00143O00143O00143O00143O00143O00143O00143O00153O00153O00153O00153O00163O00163O00163O00163O00163O00163O00163O00173O00173O00173O00173O00173O00173O00173O00193O00193O00193O00193O00193O00193O001A3O00", _ENV, ...);
end
274行目にブレークポイントを仕掛けてデバッグすると、ローカル変数にフラグが配列で格納されていた
v191[v193[2]] = #v191[v193[3]];
静的解析で最後まで解こうとしたが、ある程度わかったところで早めに動的解析へスイッチするべきだったかも